using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using FoundationLib.Ifc.DependencyInjection;

namespace FoundationLib.Ifc.Events
{
	/// <summary>
	/// Provides functionality for a central domain event handler repository.
	/// </summary>
	public static class DomainEvents
	{
		/// <summary>
		/// A list of ad-hoc event handlers.
		/// </summary>
		[ThreadStatic]
		private static IList<Delegate> actions = new List<Delegate>();

		/// <summary>
		/// The unity container containing all resolved domain event handlers.
		/// </summary>
		private static IContainer container;

		/// <summary>
		/// Gets or sets the unity container containing all automatically resolved domain
		/// event handlers.
		/// </summary>
		public static IContainer Container
		{
			get
			{
				return container;
			}
			private set
			{
				container = value;
			}
		}

		/// <summary>
		/// Clears all registered ad-hoc event handlers. This does not affect event handlers 
		/// resolved through dependency injections.
		/// </summary>
		/// <param name="disposeContainer">True if the event handler DI container should be cleared</param>
		public static void Clear(bool disposeContainer)
		{
			if (disposeContainer && container != null)
				container.Dispose();
			actions = null;
		}

		/// <summary>
		/// Raises a domain event.
		/// </summary>
		/// <param name="args">The domain event arguments</param>
		/// <typeparam name="T">The type of event raised</typeparam>
		public static void Raise<T>(T args) where T : IDomainEvent
		{
			Contract.Requires(args != null, "Event arguments must be specified.");

			// Invoking event handlers resolved by dependency injection
			if (container != null)
			{
				var handlers = container.ResolveAll<IDomainEventHandler<T>>();
				foreach (var handler in handlers)
				{
					handler.Handle(args);
				}
			}

			// Invoking event handler actions
			if (actions != null)
			{
				foreach (var item in actions)
				{
					Action<T> action = item as Action<T>;
					if (action != null)
						action(args);
				}
			}
		}

		/// <summary>
		/// Registers an ad-hoc domain event handler with the repository.
		/// </summary>
		/// <remarks>This method may be used to register ad-hoc event handler delegates, 
		/// e.g. for testing purposes. Alternatively, event handlers can be registered 
		/// by dependency injection, that is, by configuring the <see cref="IContainer"/>.</remarks>
		/// <typeparam name="T">The type of event</typeparam>
		/// <param name="handler">The domain event handler</param>
		public static void Register<T>(Action<T> handler) where T : IDomainEvent
		{
			Contract.Requires(handler != null, "A handler action must be specified.");
			actions.Add(handler);
		}

		/// <summary>
		/// Configures the singleton to use the given IoC container
		/// </summary>
		/// <param name="container">The IoC container to be used by the domain event
		/// manager</param>
		public static void Configure(IContainer container)
		{
			if (container == null)
				throw new ArgumentNullException("container");

			Container = container;
		}
	}
}